<?php
###
#  Baing PHP 框架 1.0.0
#  http://baing.co/
#
#  Copyright: 白晓阳
#  License: CC BY-NC-SA 4.0
#  https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh
###

namespace bai\resource\application;

use bai\feature\RuntimeException, bai\feature\ResourceException;


/**
 * Runtime
 * [
 *     'path' => 'path'
 *     'format' => 'format'
 *     'level' => level
 *     'maxSize' => 'max size'
 * ]
 */
class Logger extends \bai\resource\Application
{
    public const TYPE_DEFAULT = 'log';

    public const ERROR = E_USER_ERROR;
    public const WARING = E_USER_WARNING;
    public const INFO = E_USER_NOTICE;
    public const DEBUG = E_USER_DEPRECATED;
    public const ALL = E_ALL;

    public const LEVEL_INFO = [
        self::ERROR => 'ERROR',
        self::WARING => 'WARN ',
        self::INFO => 'INFO ',
        self::DEBUG => 'DEBUG',
    ];

    protected static $buffer = [];

    public static function print(string $message, int $level = self::ERROR, bool $solo = false)
    {
        if (!self::$logger) {
            self::$buffer[] = [$message, $level, new \DateTime()];
            return self::RUN_SUCCEEDED;
        }

        return self::$logger->run($message, $level, solo: $solo);
    }

    protected static function setupId(array &$runtime, string $key = ''): string
    {
        return parent::setupId($runtime, $runtime['path'] ?? $key);
    }

    public function run(string $message = '', int $level = self::INFO, ?\DateTime $datetime = null, bool $solo = false)
    {
        if (!$message) return self::RUN_FAILED;
        $base = $this->find('level') ?? self::INFO;
        if ($level > $base) return self::RUN_FAILED;

        if ($solo) {
            $result = error_log("$message\n", 3, $this->find('path'));
            return ['status' => $result ? self::SUCCEEDED : self::FAILED];
        }

        $params = $this->prepareParams($level, $datetime);
        $params['message'] = $message;
        $formated = preg_replace_callback('/:([a-zA-Z][a-zA-Z0-9]*)/', fn ($matches) => $params[$matches[1]] ?? '', $this->find('format'));
        $result = error_log($formated, 3, $this->find('path'));
        return ['status' => $result ? self::SUCCEEDED : self::FAILED];
    }

    public function error(string $message, bool $solo = false)
    {
        return $this->run($message, self::ERROR, solo: $solo);
    }

    public function warn(string $message, bool $solo = false)
    {
        return $this->run($message, self::WARING, solo: $solo);
    }

    public function info(string $message, bool $solo = false)
    {
        return $this->run($message, self::INFO, solo: $solo);
    }

    public function debug(string $message, bool $solo = false)
    {
        return $this->run($message, self::DEBUG, solo: $solo);
    }

    protected function init()
    {
        $path = $this->find('path');
        if (!$path) {
            throw new RuntimeException('path');
        }
        $root = self::config(['path', 'application', self::className()]);
        if (!$root) {
            throw new RuntimeException('root');
        }
        $fullPath = "$root/$path";
        if (!touch($fullPath)) {
            throw new ResourceException(params: $fullPath);
        }
        $this->runtime['path'] = $fullPath;

        if (static::$buffer) {
            foreach (static::$buffer as $log) {
                $this->run(...$log);
            }
            static::$buffer = [];
        }
    }

    protected function close()
    {
        if (static::$buffer) {
            if (self::$logger) {
                foreach (static::$buffer as $log) {
                    self::$logger->run(...$log);
                }
            } else {
                foreach (static::$buffer as $log) {
                    error_log($log[0] . "\n", 0);
                }
            }
            static::$buffer = [];
        }
        parent::close();
    }

    protected function prepareParams(int $level, \DateTime $datetime = null): array
    {
        $params = [
            'datetime' => ($datetime ?? new \DateTime())->format('Y-m-d H:i:s.v'),
            'level' => self::LEVEL_INFO[$level],
            'pid' => getmypid()
        ];
        if (self::$request) {
            $params['method'] = self::$request->method;
            $params['path'] = self::$request->path;
        }
        if (str_contains($this->find('format'), ':origin')) {
            $trace = debug_backtrace(limit: 4);
            $index = 2;
            $origin = $trace[$index]['class'] ?? '';
            if ($origin === static::class) {
                $origin = $trace[++$index]['class'] ?? '';
            }
            if ($origin !== static::class) {
                if ($object = $trace[$index]['object'] ?? null) {
                    $params['origin'] = $object::class === static::class ? $origin : $object::class;
                } else {
                    $params['origin'] = $origin;
                }
            }
        }
        return $params;
    }
}
